﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;

namespace iStudio.Structure.Control.Audio.Equalizer
{
    /// <summary>
    /// A control that displays and edits banded frequency amplification.
    /// </summary>
    [DisplayName ("Equalizer"), Description ("Displays and edits banded frequency amplification."), ToolboxItem (true), TemplatePart (Name = "PART_EqualizerGrid", Type = typeof (Grid))]
    
    public class Equalizer : System.Windows.Controls.Control
    {
        #region Fields

        private readonly List<EqualizerSlider> _sliders = new List<EqualizerSlider>();
        private readonly List<TextBlock> _frequencyText = new List<TextBlock> (); 

        private Grid _equalizerGrid;
        private Grid _frequencyGrid;

        public event EventHandler<SliderChangedEventArgs> SliderChangedValue;

        #endregion

        #region DependencyProperties

        #region EqualizerValues

        /// <summary>
        /// Identifies the <see cref="EqualizerValues" /> dependency property. 
        /// </summary>
        public static readonly DependencyProperty EqualizerValuesProperty = DependencyProperty.Register ("EqualizerValues", typeof (float[]), typeof (Equalizer), new UIPropertyMetadata (new[] {0f, 0f, 0f, 0f, 0f, 0f, 0f}, OnEqualizerValuesChanged, OnCoerceEqualizerValues));

        /// <summary>
        /// Gets or sets the values of each equalizer band.
        /// </summary>
        /// <remarks>The number of elements in the EqualizerValues array must be equal to the number of bands. If not, all values will be set to zero.</remarks>
        public float[] EqualizerValues
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (float[]) GetValue (EqualizerValuesProperty);
            }
            set
            {
                SetValue (EqualizerValuesProperty, value);
            }
        }

        private static object OnCoerceEqualizerValues (DependencyObject o, object value)
        {
            Equalizer equalizer = o as Equalizer;
            if (equalizer != null)
            {
                return equalizer.OnCoerceEqualizerValues ((float[]) value);
            }
            return value;
        }

        private static void OnEqualizerValuesChanged (DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Equalizer equalizer = o as Equalizer;
            if (equalizer != null)
            {
                equalizer.OnEqualizerValuesChanged ((float[]) e.OldValue, (float[]) e.NewValue);
            }
        }

        /// <summary>
        /// Coerces the value of <see cref="EqualizerValues"/> when a new value is applied.
        /// </summary>
        /// <param name="value">The value that was set on <see cref="EqualizerValues"/></param>
        /// <returns>The adjusted value of <see cref="EqualizerValues"/></returns>
        protected virtual float[] OnCoerceEqualizerValues (float[] value)
        {
            if (value == null || value.Length != NumberOfBands)
            {
                return new float[NumberOfBands];
            }
            return value;
        }

        /// <summary>
        /// Called after the <see cref="EqualizerValues"/> value has changed.
        /// </summary>
        /// <param name="oldValue">The previous value of <see cref="EqualizerValues"/></param>
        /// <param name="newValue">The new value of <see cref="EqualizerValues"/></param>
        protected virtual void OnEqualizerValuesChanged (float[] oldValue, float[] newValue)
        {
            if (newValue == null || newValue.Length != NumberOfBands)
            {
                SetEqualizerValues (new float[NumberOfBands]);
            }
            else
            {
                SetEqualizerValues (newValue);
            }
        }

        #endregion

        #region FrequencyValues

        /// <summary>
        /// Identifies the <see cref="FrequencyValues" /> dependency property. 
        /// </summary>
        public static readonly DependencyProperty FrequencyValuesProperty = DependencyProperty.Register("FrequencyValues", typeof(float[]), typeof(Equalizer), new UIPropertyMetadata(new[] { 0f, 0f, 0f, 0f, 0f, 0f, 0f }, OnFrequencyValuesChanged, OnCoerceFrequencyValues));

        /// <summary>
        /// Gets or sets the values of each Frequency band.
        /// </summary>
        /// <remarks>The number of elements in the FrequencyValues array must be equal to the number of bands. If not, all values will be set to zero.</remarks>
        public float[] FrequencyValues
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (float[])GetValue(FrequencyValuesProperty);
            }
            set
            {
                SetFrequencyValues (value);

                SetValue(FrequencyValuesProperty, value);
            }
        }

        private static object OnCoerceFrequencyValues(DependencyObject o, object value)
        {
            Equalizer equalizer = o as Equalizer;
            if (equalizer != null)
            {
                return equalizer.OnCoerceFrequencyValues((float[])value);
            }
            return value;
        }

        private static void OnFrequencyValuesChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Equalizer equalizer = o as Equalizer;
            if (equalizer != null)
            {
                equalizer.OnFrequencyValuesChanged((float[])e.OldValue, (float[])e.NewValue);
            }
        }

        /// <summary>
        /// Coerces the value of <see cref="FrequencyValues"/> when a new value is applied.
        /// </summary>
        /// <param name="value">The value that was set on <see cref="FrequencyValues"/></param>
        /// <returns>The adjusted value of <see cref="FrequencyValues"/></returns>
        protected virtual float[] OnCoerceFrequencyValues(float[] value)
        {
            if (value == null || value.Length != NumberOfBands)
            {
                return new float[NumberOfBands];
            }
            return value;
        }

        /// <summary>
        /// Called after the <see cref="FrequencyValues"/> value has changed.
        /// </summary>
        /// <param name="oldValue">The previous value of <see cref="FrequencyValues"/></param>
        /// <param name="newValue">The new value of <see cref="FrequencyValues"/></param>
        protected virtual void OnFrequencyValuesChanged(float[] oldValue, float[] newValue)
        {
            if (newValue == null || newValue.Length != NumberOfBands)
            {
                SetFrequencyValues(new float[NumberOfBands]);
            }
            else
            {
                SetFrequencyValues(newValue);
            }
        }

        #endregion

        #region NumberOfBands

        /// <summary>
        /// Identifies the <see cref="NumberOfBands" /> dependency property. 
        /// </summary>
        public static readonly DependencyProperty NumberOfBandsProperty = DependencyProperty.Register ("NumberOfBands", typeof (int), typeof (Equalizer), new UIPropertyMetadata (10, OnNumberOfBandsChanged, OnCoerceNumberOfBands));

        /// <summary>
        /// Gets or sets the number of bands that the equalizer will display.
        /// </summary>
        public int NumberOfBands
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (int) GetValue (NumberOfBandsProperty);
            }
            set
            {
                SetValue (NumberOfBandsProperty, value);
            }
        }

        private static object OnCoerceNumberOfBands (DependencyObject o, object value)
        {
            Equalizer equalizer = o as Equalizer;
            if (equalizer != null)
            {
                return equalizer.OnCoerceNumberOfBands ((int) value);
            }
            return value;
        }

        private static void OnNumberOfBandsChanged (DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Equalizer equalizer = o as Equalizer;
            if (equalizer != null)
            {
                equalizer.OnNumberOfBandsChanged ((int) e.OldValue, (int) e.NewValue);
            }
        }

        /// <summary>
        /// Coerces the value of <see cref="NumberOfBands"/> when a new value is applied.
        /// </summary>
        /// <param name="value">The value that was set on <see cref="NumberOfBands"/></param>
        /// <returns>The adjusted value of <see cref="NumberOfBands"/></returns>
        protected virtual int OnCoerceNumberOfBands (int value)
        {
            value = Math.Max (1, value);
            return value;
        }

        /// <summary>
        /// Called after the <see cref="NumberOfBands"/> value has changed.
        /// </summary>
        /// <param name="oldValue">The previous value of <see cref="NumberOfBands"/></param>
        /// <param name="newValue">The new value of <see cref="NumberOfBands"/></param>
        protected virtual void OnNumberOfBandsChanged (int oldValue, int newValue)
        {
            CreateSliders ();
            SetEqualizerValues (new float[NumberOfBands]);
        }

        #endregion

        #endregion

        #region Constructors

        static Equalizer ()
        {
            DefaultStyleKeyProperty.OverrideMetadata (typeof (Equalizer), new FrameworkPropertyMetadata (typeof (Equalizer)));
        }

        /// <summary>
        /// Creates an instance of Equalizer.
        /// </summary>
        public Equalizer ()
        {
            CreateSliders ();
        }

        #endregion

        #region Template Overrides

        /// <summary>
        /// When overridden in a derived class, is invoked whenever application code
        /// or internal processes call System.Windows.FrameworkElement.ApplyTemplate().
        /// </summary>
        public override void OnApplyTemplate ()
        {
            base.OnApplyTemplate ();

            if (_equalizerGrid != null)
            {
                _equalizerGrid.Children.Clear ();
            }

            _equalizerGrid = GetTemplateChild ("PART_EqualizerGrid") as Grid;
            _frequencyGrid = GetTemplateChild ("PART_FrequencyGrid") as Grid;

            CreateSliders ();
        }

        #endregion

        #region Private Utility Methods

        private void CreateSliders ()
        {
            if ( (_equalizerGrid == null) || (_frequencyGrid == null) )
            {
                return;
            }

            ClearSliders ();
            _equalizerGrid.ColumnDefinitions.Clear ();

            for (int i = 0; i < NumberOfBands; i++)
            {
                ColumnDefinition channelColumn = new ColumnDefinition
                                                     {
                                                         Width = new GridLength (10.0d, GridUnitType.Star)
                                                     };
                _equalizerGrid.ColumnDefinitions.Add (channelColumn);
                EqualizerSlider slider = new EqualizerSlider
                                    {
                                        Style = (Style) FindResource ("EqualizerSlider"),
                                        Maximum = 1.0,
                                        Minimum = -1.0,
                                        SmallChange = 0.01,
                                        LargeChange = 0.1,
                                        Index = i
                                    };

                ColumnDefinition channelFrequencyColumn = new ColumnDefinition
                {
                    Width = new GridLength(10.0d, GridUnitType.Star)
                };

                _frequencyGrid.ColumnDefinitions.Add (channelFrequencyColumn);

                TextBlock frequencyText;

                if (FrequencyValues.Length > i)
                {
                    frequencyText = new TextBlock (new Run (Library.Text.FrequencyToText (FrequencyValues[i])))
                                                  {
                                                      Foreground = new SolidColorBrush (Color.FromRgb (207, 207, 207)),
                                                      FontWeight = FontWeights.SemiBold,
                                                      HorizontalAlignment = HorizontalAlignment.Center
                                                  };
                }
                else
                {
                    frequencyText = new TextBlock
                    {
                        Foreground = new SolidColorBrush(Color.FromRgb(207, 207, 207)),
                        FontWeight = FontWeights.SemiBold,
                        HorizontalAlignment = HorizontalAlignment.Center
                    };
                }

                Grid.SetColumn (frequencyText, i);
                Grid.SetColumn (slider, i);

                _sliders.Add (slider);
                _frequencyText.Add (frequencyText);

                _equalizerGrid.Children.Add (slider);
                _frequencyGrid.Children.Add (frequencyText);
                slider.ValueChanged += SliderValueChanged;
            }
        }

        private void SliderValueChanged (object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            EqualizerValues = GetEqualizerValues ();

            SliderChangedValue(this, new SliderChangedEventArgs
                                         {
                                             Index = ((EqualizerSlider)sender).Index,
                                             Value = EqualizerValues[((EqualizerSlider)sender).Index]
                                         });
        }

        private void ClearSliders ()
        {
            foreach (EqualizerSlider slider in _sliders)
            {
                slider.ValueChanged -= SliderValueChanged;
                _equalizerGrid.Children.Remove (slider);
            }

            foreach (TextBlock frequency in _frequencyText)
            {
                _frequencyGrid.Children.Remove (frequency);
            }

            _frequencyText.Clear ();
            _sliders.Clear ();
        }

        private float[] GetEqualizerValues ()
        {
            float[] sliderValues = new float[NumberOfBands];
            for (int i = 0; i < NumberOfBands; i++)
            {
                sliderValues[i] = (float) _sliders[i].Value;
            }
            return sliderValues;
        }

        private void SetEqualizerValues (float[] values)
        {
            if (_sliders.Count == 0)
            {
                return;
            }

            for (int i = 0; i < NumberOfBands; i++)
            {
                _sliders[i].Value = values[i];
            }
        }

        private void SetFrequencyValues(float[] values)
        {
            if (_frequencyText.Count == 0)
            {
                return;
            }

            for (int i = 0; i < NumberOfBands; i++)
            {
                _frequencyText[i].Text = Library.Text.FrequencyToText(values[i]);
            }
        }

        #endregion
    }

    /// <summary>
    /// Implement eventing system
    /// </summary>
    public class SliderChangedEventArgs : EventArgs
    {
        public double Value
        {
            get;
            set;
        }

        public int Index
        {
            get;
            set;
        }
    }
}